/*
 * Copyright (c) zhg2yqq.com Corp.
 * All Rights Reserved.
 */
package com.zhg2yqq.wheels.log.aop;

import java.util.Arrays;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.CodeSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONPath;
import com.alibaba.fastjson2.JSONWriter;
import com.zhg2yqq.wheels.log.annotation.OperationLog;
import com.zhg2yqq.wheels.log.annotation.OperationLog.OperationLogs;
import com.zhg2yqq.wheels.log.dto.LogInfoDTO;
import com.zhg2yqq.wheels.log.service.IOperationLogService;

/**
 * 记录日志切面处理
 *
 * @author zhg2yqq, 2022年11月30日
 * @version zhg2yqq v1.0
 */
@Aspect
public class OperationLogAspect {
    private Logger logger = LoggerFactory.getLogger(OperationLogAspect.class);

    private IOperationLogService operationLogService;

    public OperationLogAspect(IOperationLogService operationLogService) {
        this.operationLogService = operationLogService;
    }

    @Around(value = "@annotation(operationLog)")
    public Object saveLog(ProceedingJoinPoint point, OperationLog operationLog) throws Throwable {
        if (operationLog.enable()) {
            // 复制参数封装对象
            Object[] params = point.getArgs();
            Signature sig = point.getSignature();

            String[] paramNames = ((CodeSignature) sig).getParameterNames();
            Map<String, Object> parameters;
            if (paramNames != null) {
                parameters = new LinkedHashMap<>(paramNames.length);
                for (int i = 0; i < paramNames.length; i++) {
                    parameters.put(paramNames[i], params[i]);
                }
            } else {
                parameters = new LinkedHashMap<>(params.length);
                for (int i = 0; i < params.length; i++) {
                    parameters.put(String.valueOf(i), params[i]);
                }
            }

            // 调用代理方法
            Date start = new Date();
            Object result = null;
            Throwable error = null;
            try {
                result = point.proceed();
            } catch (Throwable e) {
                error = e;
                throw e;
            } finally {
                Date end = new Date();
                try {
                    this.saveLog(error, start, end, parameters, result, operationLog, point);
                } catch (Exception e) {
                    logger.error("记录操作日志错误", e);
                }
            }
            return result;
        } else {
            return point.proceed();
        }
    }

    @Around("@annotation(operationLogs)")
    public Object saveLogs(ProceedingJoinPoint point, OperationLogs operationLogs)
            throws Throwable {
        if (Arrays.stream(operationLogs.value()).anyMatch(OperationLog::enable)) {
            // 复制参数封装对象
            Object[] params = point.getArgs();
            Signature sig = point.getSignature();
            String[] paramNames = ((CodeSignature) sig).getParameterNames();
            Map<String, Object> parameters = new LinkedHashMap<>(paramNames.length);
            for (int i = 0; i < paramNames.length; i++) {
                parameters.put(paramNames[i], params[i]);
            }

            // 调用代理方法
            Date start = new Date();
            Object result = null;
            Throwable error = null;
            try {
                result = point.proceed();
            } catch (Throwable e) {
                error = e;
                throw e;
            } finally {
                Date end = new Date();
                // 复制结果
                for (OperationLog operationLog : operationLogs.value()) {
                    try {
                        this.saveLog(error, start, end, parameters, result, operationLog,
                                point);
                    } catch (Exception e) {
                        logger.error("记录操作日志错误", e);
                    }
                }
            }
            return result;
        } else {
            return point.proceed();
        }
    }

    private void saveLog(Date start, Date end, Map<String, Object> parameter, Object result,
                         OperationLog operationLog, JoinPoint point) {
        this.saveLog(null, start, end, parameter, result, operationLog, point);
    }

    private void saveLog(Throwable error, Date start, Date end, Map<String, Object> parameter, Object result,
                         OperationLog operationLog, JoinPoint point) {
        if (!operationLog.enable()) {
            return;
        }

        LogInfoDTO logInfo = new LogInfoDTO();
        logInfo.setError(error);
        logInfo.setStartDate(start);
        logInfo.setEndDate(end);
        logInfo.setAnnotation(operationLog);

        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
                .getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        logInfo.setRequest(request);
        HttpServletResponse response = attributes.getResponse();
        logInfo.setResponse(response);

        Signature sig = point.getSignature();
        String functionName = String.join(".", sig.getDeclaringTypeName(), sig.getName());
        logInfo.setFunctionName(functionName);

        if (operationLog.logReturn()) {
            Object object = JSON.toJSON(result, JSONWriter.Feature.WriteMapNullValue,
                    JSONWriter.Feature.ReferenceDetection, JSONWriter.Feature.IgnoreErrorGetter);
            String[] ignores = operationLog.ignoreReturn();
            if (ignores != null && ignores.length > 0) {
                for (String path : ignores) {
                    JSONPath.remove(object, path);
                }
            }
            logInfo.setResult(String.valueOf(object));
        }
        if (operationLog.logParameter()) {
            Object object = JSON.toJSON(parameter, JSONWriter.Feature.WriteMapNullValue,
                    JSONWriter.Feature.ReferenceDetection, JSONWriter.Feature.IgnoreErrorGetter);
            String[] ignores = operationLog.ignoreParameter();
            if (ignores != null && ignores.length > 0) {
                for (String path : ignores) {
                    JSONPath.remove(object, path);
                }
            }
            logInfo.setParameter(JSON.toJSONString(object, JSONWriter.Feature.WriteMapNullValue));
        }
        operationLogService.saveOperationLog(logInfo);
    }
}
